/*
 *  (c) E.S.A.
 */
#include <sys/socket.h>
#include <sys/select.h>
#include <arpa/inet.h>
#include <sys/types.h>

#include <stdlib.h>

#include <iostream>

#include "server_mode.h"
#include "msg_logger.h"
#include "socket_utilities.h"
#include "global_data.h"


/**        TCP connection 
 *    Waiting for clients-nodes to connect
 */
void* server_conn_main (void *arg) 
{

	fd_set readfds, writefds,exceptfds,
	tmp_readfds, tmp_writefds;
	int	acceptfd, maxfd, inputid = 0;
	struct	sockaddr_in cliaddr, tmp_cliaddr;
	socklen_t cliaddr_size = sizeof(cliaddr);
	struct timeval	timeout;
	int n, i;

	r_client_data *new_client, *remote_client;
	int len; 
	uint32_t nbo_mess_size;
	char *rcv_mess;
	char s[10];

	char *cli_ip = new char;

	/* create listening fd for TCP connections */
	MSG_LOG (DBG, "listening on port " << global.local_addr.port);
	int listenfd = listen_on_socket (global.local_addr.port, BACK_LOG);
	if(listenfd < 0)
		goto s_m_thd_end;
	maxfd = listenfd;

	rcv_mess = (char*)calloc( MESS_SIZE, sizeof(char));
	new_client = new r_client_data; 

	// add the file descriptor sock to the read/write/except set  
	{
		FD_ZERO(&readfds);
		FD_SET(listenfd, &readfds);
		FD_SET(inputid, &readfds);

		FD_ZERO(&writefds);
		FD_SET(listenfd, &writefds);

		FD_ZERO(&exceptfds);
		FD_SET(listenfd, &exceptfds);
	}

	// wait up to 1 second for a response 
	timeout.tv_sec = 1;
	timeout.tv_usec = 0;

	while (!global.quit_flag) {
		tmp_readfds = readfds;
		tmp_writefds = writefds;

		n = select(maxfd+1, &tmp_readfds, &tmp_writefds, &exceptfds, &timeout); 
		if (n < 0){
			MSG_LOG (ERR, "Error: failed at select");
			goto s_m_thd_end;
		}
		if (n == 0);


		for(i=0; i<=maxfd; i++) {

			if (FD_ISSET(i, &tmp_readfds)) {

				if(i == listenfd){
					///////////////  NEW CONNECTION /////////////// 
					MSG_LOG (DBG, "got new connection");

					// accept new connection:
					acceptfd = 
						accept(listenfd, reinterpret_cast<sockaddr*>(&cliaddr), &cliaddr_size);
					if(acceptfd < 0){
						MSG_LOG (ERR, "accept on socket failed");
						goto s_m_thd_end;
					}

					// add new fd to the reading set and writing set
					FD_SET(acceptfd, &readfds);
					FD_SET(acceptfd, &writefds);
					if (acceptfd > maxfd)
						maxfd = acceptfd;

					// add new client 
					new_client->mess_size = 0;
					new_client->read_size_flag = true;
					new_client->socket = acceptfd;
					new_client->ip = new_client->ip.insert(0, inet_ntoa(cliaddr.sin_addr));
					global.list_of_clients.add_element(new_client);

				} 
				else
					if(i == inputid){
						//////////////////  KEYBOARD  ////////////////// 
						MSG_LOG (DBG, "got something from the keyboard");

						fgets(s, sizeof(s), stdin);
						global.quit_flag = true;

					}else {
						//////////////////  READ DATA  ///////////////////

						// get ip from socket 
						if (getsockname(i, reinterpret_cast<sockaddr*>(&tmp_cliaddr), &cliaddr_size) < 0) {
							MSG_LOG (ERR, "Error at getsockname"); 
							break;
						}
						(void) strcpy(cli_ip, inet_ntoa(tmp_cliaddr.sin_addr));
						remote_client = global.list_of_clients.get_element(cli_ip);

						MSG_LOG (DBG, "something to read from socket");

						if (remote_client == NULL){
							MSG_LOG (ERR, "Error: This is not an active socket"); break;
						}

						if (remote_client->read_size_flag == true){

							// read message size
							len = sizeof(uint32_t);
							remote_client->mess_size =  0;
							int rez = 
								receive_data_on_socket (i, &nbo_mess_size, len);
							if (rez != 0) {
								close(i);
								break;
							}
							if(len == 0){
								/* Client was closed => close connection */
								MSG_LOG (INFO, "Closing connection with the other end.. ");
								close(i);
								FD_CLR(i, &readfds);
								FD_CLR(i, &writefds);
								if (i == maxfd){
									while (FD_ISSET(maxfd, &readfds) != 0)
										maxfd -= 1;
								}

								/* Erase element */
								global.list_of_clients.del_element(cli_ip);
								MSG_LOG (DBG, "Erased elem. from remote client list");

							}

							remote_client->mess_size = ntohl(nbo_mess_size);
							MSG_LOG (DBG, "Will read " << remote_client->mess_size
									<< " bytes .. (from sock = " << i << " ) ");
							remote_client->read_size_flag = false;
						} 
						else { 

							// receive data from client 
							bzero (rcv_mess, MESS_SIZE * sizeof(char));
							len = remote_client->mess_size;

							int rez = 
								receive_data_on_socket (i, rcv_mess, len);
							if (rez != 0) {
								close(i);
								global.quit_flag = true;
								break;
							}

							if(len == 0){

								/* Client was closed => close connection */
								MSG_LOG (DBG, "Closing connection with the other end.. ");
								close(i);
								FD_CLR(i, &readfds);
								FD_CLR(i, &writefds);
								if (i == maxfd){
									while (FD_ISSET(maxfd, &readfds) != 0)
										maxfd -= 1;
								}

								/* Erase element */
								global.list_of_clients.del_element(cli_ip);
								MSG_LOG (DBG, "Erased elem. from remote client list");

							}
							else {

								MSG_LOG (DBG, "MUST read " << remote_client->mess_size << 
										" read  " << len); 

								////////////////  receive data from REMOTE CLIENTS (@p) ///////////////////
								sem_wait( &global.recv_buff.empty_sem );
								sem_wait( &global.recv_buff.put_mutex );

								global.recv_buff.mess[global.recv_buff.nextin] = rcv_mess;
								global.recv_buff.nextin = (global.recv_buff.nextin + 1) % CIRCULAR_BUF_SIZE;

								sem_post( &global.recv_buff.put_mutex );
								sem_post( &global.recv_buff.full_sem );

								remote_client->read_size_flag = true;
								
							}

						}

					}

			} // readfds

			if (FD_ISSET(i, &tmp_writefds)) {
				//////////////////  WRITE DATA  ////////////////// 

				////////////////  send data to REMOTE CLIENTS (@c) ///////////////////
				if ( sem_trywait( &global.cli_send_buff.full_sem ) == 0 ) { 
					sem_wait( &global.cli_send_buff.get_mutex);
					MSG_LOG (DBG, "got something to write to remote party ");

					// get ip from socket 
					if (getsockname(i, reinterpret_cast<sockaddr*>(&tmp_cliaddr), &cliaddr_size) < 0) {
						MSG_LOG (ERR, "Error at getsockname"); 
						break;
					}
					(void) strcpy(cli_ip, inet_ntoa(tmp_cliaddr.sin_addr));
					remote_client = global.list_of_clients.get_element(cli_ip);

					// send message on socket
					send_data_len_prefixed (i, 
							global.cli_send_buff.mess[global.cli_send_buff.nextout]);

					global.cli_send_buff.nextout = (global.cli_send_buff.nextout + 1) % CIRCULAR_BUF_SIZE;

					sem_post ( &global.cli_send_buff.get_mutex);
					sem_post ( &global.cli_send_buff.empty_sem);

				}


			} // writefds
		} // for all sockets id
	}

	s_m_thd_end:
	free(rcv_mess);
	delete new_client;
	/* close the listening socket */
	if (listenfd > 0)
		close(listenfd);
	global.quit_flag = true;

	MSG_LOG (INFO, "server mode thread finishing .. ");
	return NULL;

}

